// Scintilla source code edit control
/** @file LexVB.cxx
 ** Lexer for Visual Basic and VBScript.
 **/
// Copyright 1998-2005 by Neil Hodgson <neilh@scintilla.org>
// The License.txt file describes the conditions under which this software may be distributed.

#include <cassert>
#include <cstring>

#include <string>
#include <string_view>
#include <vector>

#include "ILexer.h"
#include "Scintilla.h"
#include "SciLexer.h"

#include "WordList.h"
#include "LexAccessor.h"
#include "Accessor.h"
#include "StyleContext.h"
#include "CharacterSet.h"
#include "StringUtils.h"
#include "LexerModule.h"

using namespace Lexilla;

namespace {

enum class Language {
	VBNET,
	VBA,
	VBScript,
};

enum class KeywordType {
	None,
	End,
	Function,
	Select,			// Select Case
	Property,		// Property Get, Set, Let
	SkipWhile,		// Do While, Loop While, Skip While, Take While
	CustomEvent,	// Custom Event
	Preprocessor,
};

enum {
	VBLineType_CommentLine = 1,
	VBLineType_DimLine = 2,
	VBLineType_ConstLine = 3,
	VBLineStateStringInterpolation = 4,
	VBLineStateLineContinuation = 1 << 3,
	VBLineStateInterfaceBlock = 1 << 4,
	VBLineStatePropertyBlock = 1 << 5,
	VBLineStateMultilineMask = VBLineStateLineContinuation | VBLineStateInterfaceBlock | VBLineStatePropertyBlock,
};

//KeywordIndex++Autogenerated -- start of section automatically generated
enum {
	KeywordIndex_Keyword = 0,
	KeywordIndex_TypeKeyword = 1,
	KeywordIndex_VBAKeyword = 2,
	KeywordIndex_Preprocessor = 3,
	KeywordIndex_Attribute = 4,
	KeywordIndex_Class = 5,
	KeywordIndex_Interface = 6,
	KeywordIndex_Enumeration = 7,
	KeywordIndex_Constant = 8,
	KeywordIndex_BasicFunction = 9,
	MaxKeywordSize = 32,
};
//KeywordIndex--Autogenerated -- end of section automatically generated

// https://learn.microsoft.com/en-us/dotnet/visual-basic/reference/language-specification/lexical-grammar#type-characters
// https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/data-type-summary
constexpr bool IsTypeCharacter(int ch) noexcept {
	return ch == '%' // Integer
		|| ch == '&' // Long
		|| ch == '^' // VBA LongLong
		|| ch == '@' // Decimal, VBA Currency
		|| ch == '!' // Single
		|| ch == '#' // Double
		|| ch == '$';// String
}

constexpr bool IsVBNumberPrefix(int ch) noexcept {
	ch = UnsafeLower(ch);
	return ch == 'h' // Hexadecimal
		|| ch == 'o' // Octal
		|| ch == 'b';// Binary
}

constexpr bool PreferStringConcat(int chPrevNonWhite, int stylePrevNonWhite) noexcept {
	return chPrevNonWhite == '\"' || chPrevNonWhite == ')' || chPrevNonWhite == ']'
		|| (stylePrevNonWhite != SCE_VB_KEYWORD && IsIdentifierChar(chPrevNonWhite));
}

constexpr bool IsSpaceEquiv(int state) noexcept {
	return state <= SCE_VB_LINE_CONTINUATION;
}

// https://docs.microsoft.com/en-us/dotnet/standard/base-types/composite-formatting
constexpr bool IsInvalidFormatSpecifier(int ch) noexcept {
	// Custom format strings allows any characters
	return (ch >= '\0' && ch < ' ') || ch == '\"' || ch == '{' || ch == '}';
}

inline bool IsInterpolatedStringEnd(const StyleContext &sc) noexcept {
	return sc.ch == '}' || sc.ch == ':'
		|| (sc.ch == ',' && (IsADigit(sc.chNext) || (sc.chNext == '-' && IsADigit(sc.GetRelative(2)))));
}

void ColouriseVBDoc(Sci_PositionU startPos, Sci_Position lengthDoc, int initStyle, LexerWordList keywordLists, Accessor &styler) {
	KeywordType kwType = KeywordType::None;
	bool preprocessor = false;
	bool declareDelegate = false; // Declare, Delegate {Function Sub}
	bool openFor = false; // VBA: Open For Access
	int lineState = 0;
	int parenCount = 0;
	int beginEnd = 0; // VB6 Form: nested Begin ... End
	int fileNbDigits = 0;
	int visibleChars = 0;
	int chBefore = 0;
	int chPrevNonWhite = 0;
	int stylePrevNonWhite = SCE_VB_DEFAULT;
	std::vector<int> nestedState;

	const Language language = static_cast<Language>(styler.GetPropertyInt("lexer.lang"));
	if (startPos != 0) {
		// backtrack to the line starts expression inside interpolated string literal.
		BacktrackToStart(styler, VBLineStateStringInterpolation, startPos, lengthDoc, initStyle);
	}

	StyleContext sc(startPos, lengthDoc, initStyle, styler);
	if (sc.currentLine > 0) {
		lineState = styler.GetLineState(sc.currentLine - 1);
		beginEnd = (lineState >> 8) & 0xff;
		parenCount = lineState >> 16;
		lineState &= VBLineStateMultilineMask;
	}
	if (startPos != 0 && IsSpaceEquiv(initStyle)) {
		LookbackNonWhite(styler, startPos, SCE_VB_LINE_CONTINUATION, chPrevNonWhite, stylePrevNonWhite);
	}

	while (sc.More()) {
		switch (sc.state) {
		case SCE_VB_OPERATOR:
		case SCE_VB_OPERATOR2:
		case SCE_VB_LINE_CONTINUATION:
			sc.SetState(SCE_VB_DEFAULT);
			break;

		case SCE_VB_IDENTIFIER:
			if (!IsIdentifierCharEx(sc.ch)) {
				// In Basic (except VBScript), a variable name or a function name
				// can end with a special character indicating the type of the value
				// held or returned.
				bool skipType = false;
				if (sc.ch == ']' || (language != Language::VBScript && IsTypeCharacter(sc.ch))) {
					skipType = sc.ch != ']';
					++visibleChars; // bracketed [keyword] identifier
					sc.Forward();
				}
				char s[MaxKeywordSize];
				sc.GetCurrentLowered(s, sizeof(s));
				const Sci_Position len = sc.LengthCurrent();
				if (skipType && len < MaxKeywordSize) {
					s[len - 1] = '\0';
				}
				if (StrEqual(s, "rem")) { // ignore type character after `rem`
					sc.ChangeState(SCE_VB_COMMENTLINE);
					break;
				}

				const KeywordType kwPrev = kwType;
				kwType = KeywordType::None;
				if (s[0] == '#') {
					if (keywordLists[KeywordIndex_Preprocessor].InList(s + 1)) {
						preprocessor = true;
						sc.ChangeState(SCE_VB_PREPROCESSOR);
						if (StrEqual(s + 1, "end")) {
							kwType = KeywordType::Preprocessor;
						}
					} else {
						sc.ChangeState(SCE_VB_DATE);
						continue;
					}
				} else if (kwPrev == KeywordType::Preprocessor) {
					sc.ChangeState(SCE_VB_PREPROCESSOR_WORD);
				} else {
					const int chNext = sc.GetLineNextChar();
					if (s[0] != '[') {
						if (keywordLists[KeywordIndex_Keyword].InListPrefixed(s, '(')) {
							sc.ChangeState(SCE_VB_KEYWORD3);
							if (!skipType && chBefore != '.') {
								sc.ChangeState(SCE_VB_KEYWORD);
								if (StrEqual(s, "if")) {
									if (language == Language::VBNET && chNext == '(' && (parenCount != 0 || visibleChars > 2)) {
										sc.ChangeState(SCE_VB_KEYWORD3); // If operator
									}
								} else if (StrEqual(s, "then")) {
									if (preprocessor) {
										sc.ChangeState(SCE_VB_PREPROCESSOR_WORD);
									}
								} else if (StrEqual(s, "dim")) {
									lineState |= VBLineType_DimLine;
								} else if (StrEqual(s, "const")) {
									lineState |= VBLineType_ConstLine;
								} else if (StrEqual(s, "exit")) {
									kwType = KeywordType::End;
								} else if (StrEqual(s, "end")) {
									kwType = KeywordType::End;
									if (beginEnd > 0) {
										--beginEnd;
									} else if (!IsAlpha(chNext)) {
										sc.ChangeState(SCE_VB_KEYWORD3);
									}
								} else if (StrEqualsAny(s, "sub", "function")) {
									if (kwPrev != KeywordType::End && chNext != '(') {
										kwType = KeywordType::Function;
									}
									if (declareDelegate || (lineState & VBLineStateInterfaceBlock) != 0) {
										declareDelegate = false;
										sc.ChangeState(SCE_VB_KEYWORD3);
									}
								} else if (language != Language::VBScript) {
									if (StrEqual(s, "declare") || (language == Language::VBNET && StrEqual(s, "delegate"))) {
										declareDelegate = true;
									} else if (StrEqual(s, "class")) {
										if (language == Language::VBA || (lineState & VBLineStateInterfaceBlock) != 0) {
											sc.ChangeState(SCE_VB_KEYWORD3);
										}
									} else if (language == Language::VBNET) {
										if (StrEqualsAny(s, "interface", "property")) {
											if (kwPrev == KeywordType::End) {
												lineState = 0;
											} else if ((lineState & VBLineStateInterfaceBlock) != 0) {
												sc.ChangeState(SCE_VB_KEYWORD3);
											} else {
												lineState |= (s[0] == 'i') ? VBLineStateInterfaceBlock : VBLineStatePropertyBlock;
											}
										}
									} else if (openFor && StrEqual(s, "for")) {
										openFor = false;
										sc.ChangeState(SCE_VB_KEYWORD3);
									}
								}
							}
						} else if (keywordLists[KeywordIndex_VBAKeyword].InList(s)) {
							sc.ChangeState(SCE_VB_KEYWORD3);
							if (language == Language::VBA && !skipType && chBefore != '.') {
								sc.ChangeState(SCE_VB_KEYWORD);
								if (StrEqual(s, "open")) {
									openFor = true;
								} else if (visibleChars == 5 && StrEqual(s, "begin")) {
									++beginEnd;
								}
							}
						} else if (keywordLists[KeywordIndex_TypeKeyword].InList(s)) {
							sc.ChangeState(SCE_VB_KEYWORD2);
						} else if (keywordLists[KeywordIndex_Class].InList(s)) {
							sc.ChangeState(SCE_VB_CLASS);
						} else if (keywordLists[KeywordIndex_Interface].InList(s)) {
							sc.ChangeState(SCE_VB_INTERFACE);
						} else if (keywordLists[KeywordIndex_Enumeration].InList(s)) {
							sc.ChangeState(SCE_VB_ENUM);
						} else if (keywordLists[KeywordIndex_Attribute].InListPrefixed(s, '(')) {
							sc.ChangeState(SCE_VB_ATTRIBUTE);
						} else if (keywordLists[KeywordIndex_Constant].InList(s)) {
							sc.ChangeState(SCE_VB_CONSTANT);
						} else if (keywordLists[KeywordIndex_BasicFunction].InListPrefixed(s, '(')) {
							sc.ChangeState(SCE_VB_BASIC_FUNCTION);
						}
					}
					if (sc.state == SCE_VB_IDENTIFIER) {
						if (visibleChars == len && chNext == ':') {
							sc.ChangeState(SCE_VB_LABEL);
						} else if (kwPrev == KeywordType::Function) {
							sc.ChangeState(SCE_VB_FUNCTION_DEFINITION);
						}
					}
				}
				stylePrevNonWhite = sc.state;
				sc.SetState(SCE_VB_DEFAULT);
			}
			break;

		case SCE_VB_NUMBER:
			if (!IsDecimalNumber(sc.chPrev, sc.ch, sc.chNext)) {
				if (language != Language::VBScript && IsTypeCharacter(sc.ch)) {
					sc.Forward();
				}
				sc.SetState(SCE_VB_DEFAULT);
			}
			break;

		case SCE_VB_STRING:
		case SCE_VB_INTERPOLATED_STRING:
			if (sc.atLineStart && language != Language::VBNET) {
				// multiline since VB.NET 14
				sc.SetState(SCE_VB_DEFAULT);
			} else if (sc.ch == '\"') {
				if (sc.chNext == '\"') {
					sc.Forward();
				} else {
					if (sc.chNext == 'c' || sc.chNext == 'C' || sc.chNext == '$') {
						sc.Forward();
					}
					chPrevNonWhite = sc.ch;
					sc.ForwardSetState(SCE_VB_DEFAULT);
				}
			} else if (sc.state == SCE_VB_INTERPOLATED_STRING) {
				if (sc.ch == '{') {
					if (sc.chNext == '{') {
						sc.Forward();
					} else {
						++parenCount;
						nestedState.push_back(0);
						sc.SetState(SCE_VB_OPERATOR2);
						sc.ForwardSetState(SCE_VB_DEFAULT);
					}
				} else if (sc.ch == '}') {
					if (!nestedState.empty()) {
						--parenCount;
						nestedState.pop_back();
						sc.SetState(SCE_VB_OPERATOR2);
						sc.ForwardSetState(SCE_VB_INTERPOLATED_STRING);
						continue;
					}
					if (sc.chNext == '}') {
						sc.Forward();
					}
				}
			}
			break;

		case SCE_VB_COMMENTLINE:
			if (sc.atLineStart) {
				if (lineState & VBLineStateLineContinuation) {
					lineState &= ~VBLineStateLineContinuation;
					lineState |= VBLineType_CommentLine;
				} else {
					sc.SetState(SCE_VB_DEFAULT);
				}
			} else if (language == Language::VBA && sc.ch == '_' && sc.chPrev <= ' ') {
				if (sc.GetLineNextChar(true) == '\0') {
					lineState |= VBLineStateLineContinuation;
					sc.SetState(SCE_VB_LINE_CONTINUATION);
					sc.ForwardSetState(SCE_VB_COMMENTLINE);
				}
			}
			break;

		case SCE_VB_FILENUMBER:
			if (IsADigit(sc.ch)) {
				fileNbDigits++;
				if (fileNbDigits > 3) {
					sc.ChangeState(SCE_VB_DATE);
				}
			} else if (sc.ch == '\r' || sc.ch == '\n' || sc.ch == ',') {
				// Regular uses: Close #1; Put #1, ...; Get #1, ... etc.
				// Too bad if date is format #27, Oct, 2003# or something like that...
				// Use regular number state
				sc.ChangeState(SCE_VB_NUMBER);
				sc.SetState(SCE_VB_DEFAULT);
			} else {
				sc.ChangeState(SCE_VB_DATE);
				continue;
			}
			break;

		case SCE_VB_DATE:
			if (sc.atLineStart) {
				sc.SetState(SCE_VB_DEFAULT);
			} else if (sc.ch == '#') {
				chPrevNonWhite = sc.ch;
				sc.ForwardSetState(SCE_VB_DEFAULT);
			}
			break;

		case SCE_VB_FORMAT_SPECIFIER:
			if (IsInvalidFormatSpecifier(sc.ch)) {
				sc.SetState(SCE_VB_INTERPOLATED_STRING);
				continue;
			}
			break;
		}

		if (sc.state == SCE_VB_DEFAULT) {
			if (sc.ch == '\'') {
				sc.SetState(SCE_VB_COMMENTLINE);
				if (visibleChars == 0) {
					lineState |= VBLineType_CommentLine;
				}
			} else if (sc.ch == '\"') {
				sc.SetState(SCE_VB_STRING);
			} else if (language == Language::VBNET && sc.Match('$', '"')) {
				sc.SetState(SCE_VB_INTERPOLATED_STRING);
				sc.Forward();
			} else if (sc.ch == '#') {
				if (visibleChars == 0 && language != Language::VBScript && IsUpperOrLowerCase(sc.chNext)) {
					sc.SetState(SCE_VB_IDENTIFIER);
				} else {
					fileNbDigits = 0;
					sc.SetState(SCE_VB_FILENUMBER);
				}
			} else if (sc.ch == '&' && IsVBNumberPrefix(sc.chNext) && !PreferStringConcat(chPrevNonWhite, stylePrevNonWhite)) {
				sc.SetState(SCE_VB_NUMBER);
				sc.Forward();
			} else if (IsNumberStart(sc.ch, sc.chNext)) {
				sc.SetState(SCE_VB_NUMBER);
			} else if (sc.ch == '_' && sc.chNext <= ' '/* && (sc.chPrev <= ' ' || language == Language::VBScript)*/) {
				sc.SetState(SCE_VB_LINE_CONTINUATION);
			} else if (IsIdentifierStartEx(sc.ch) || sc.ch == '[') { // bracketed [keyword] identifier
				chBefore = chPrevNonWhite;
				sc.SetState(SCE_VB_IDENTIFIER);
			} else if (IsAGraphic(sc.ch)) {
				sc.SetState(SCE_VB_OPERATOR);
				if (nestedState.empty()) {
					if (sc.ch == '(') {
						++parenCount;
					} else if (sc.ch == ')' && parenCount > 0) {
						--parenCount;
					}
				} else {
					sc.ChangeState(SCE_VB_OPERATOR2);
					if (sc.ch == '(') {
						nestedState.back() += 1;
					} else if (sc.ch == ')') {
						nestedState.back() -= 1;
					}
					if (nestedState.back() <= 0 && IsInterpolatedStringEnd(sc)) {
						sc.ChangeState((sc.ch == '}') ? SCE_VB_INTERPOLATED_STRING : SCE_VB_FORMAT_SPECIFIER);
						continue;
					}
				}
			}
		}

		if (!isspacechar(sc.ch)) {
			visibleChars++;
			if (!IsSpaceEquiv(sc.state)) {
				chPrevNonWhite = sc.ch;
				stylePrevNonWhite = sc.state;
			}
		}
		if (sc.atLineEnd) {
			if (!nestedState.empty()) {
				lineState |= VBLineStateStringInterpolation;
			}
			styler.SetLineState(sc.currentLine, lineState | (beginEnd << 8) | (parenCount << 16));
			lineState &= VBLineStateMultilineMask;
			visibleChars = 0;
			kwType = KeywordType::None;
			preprocessor = false;
			declareDelegate = false;
			openFor = false;
		}
		sc.Forward();
	}

	sc.Complete();
}

struct FoldLineState {
	int lineState;
	constexpr explicit FoldLineState(int lineState_) noexcept : lineState(lineState_) {}
	int GetLineType() const noexcept {
		return lineState & 3;
	}
	int IsInterfaceBlock() const noexcept {
		return (lineState & VBLineStateInterfaceBlock);
	}
	int IsPropertyBlock() const noexcept {
		return (lineState & VBLineStatePropertyBlock);
	}
};

inline bool IsCodeFolding(const char *s, unsigned wordLen) noexcept {
	const char *p = strstr(" interface module namespace operator synclock try using ", s);
	return p != nullptr && p[-1] == ' ' && p[wordLen] == ' ';
}

void FoldVBDoc(Sci_PositionU startPos, Sci_Position lengthDoc, int initStyle, LexerWordList /*keywordLists*/, Accessor &styler) {
	const Language language = static_cast<Language>(styler.GetPropertyInt("lexer.lang"));
	const Sci_PositionU endPos = startPos + lengthDoc;
	Sci_Line lineCurrent = styler.GetLine(startPos);
	FoldLineState foldPrev(0);
	int levelCurrent = SC_FOLDLEVELBASE;
	if (lineCurrent > 0) {
		levelCurrent = styler.LevelAt(lineCurrent - 1) >> 16;
		foldPrev = FoldLineState(styler.GetLineState(lineCurrent - 1));
	}

	int levelNext = levelCurrent;
	initStyle = styler.StyleIndexAt(startPos);
	FoldLineState foldCurrent(styler.GetLineState(lineCurrent));
	Sci_PositionU lineStartNext = styler.LineStart(lineCurrent + 1);

	KeywordType kwType = KeywordType::None;
	bool lambdaExpr = false;
	int ifThenMask = 0; // If ... Then ... End If
	int wordLen = 0;
	char s[MaxKeywordSize];
	memset(s, 0, 4);

	while (startPos < endPos) {
		const int style = initStyle;
		const char ch = styler[startPos];
		initStyle = styler.StyleIndexAt(++startPos);

		if (style == SCE_VB_KEYWORD || style == SCE_VB_PREPROCESSOR) {
			if (wordLen < MaxKeywordSize - 1) {
				s[wordLen++] = UnsafeLower(ch);
			}
			if (style != initStyle) {
				s[wordLen] = '\0';
				if (style == SCE_VB_KEYWORD) {
					const KeywordType kwPrev = kwType;
					kwType = KeywordType::None;
					if (StrEqual(s, "end") || (language == Language::VBA && StrStartsWith(s, "end"))) {
						kwType = KeywordType::End;
						levelNext--;
						// one line: If ... Then ... End If
						if (ifThenMask == 3) {
							levelNext++;
						}
						ifThenMask = 0;
					} else if (StrEqualsAny(s, "exit", "resume")) {
						kwType = KeywordType::End;
					} else if (kwPrev != KeywordType::End) {
						const int chNext = LexGetNextChar(styler, startPos, lineStartNext);
						if (StrEqual(s, "if")) {
							ifThenMask = 1;
							levelNext++;
						} else if (StrEqualsAny(s, "for", "class")) {
							levelNext++;
						} else if (StrEqualsAny(s, "next", "wend")) {
							levelNext--;
						} else if (StrEqual(s, "do")) {
							kwType = KeywordType::SkipWhile;
							levelNext++;
						} else if (StrEqual(s, "loop")) {
							kwType = KeywordType::SkipWhile;
							levelNext--;
						} else if (StrEqual(s, "case")) {
							if (kwPrev == KeywordType::Select) {
								levelNext++;
							}
						} else if (StrEqual(s, "while")) {
							if (kwPrev != KeywordType::SkipWhile) {
								levelNext++;
							}
						} else if (StrEqualsAny(s, "get", "set", "let")) {
							if (kwPrev == KeywordType::Property || (foldCurrent.IsPropertyBlock() && !StrEqual(s, "let"))) {
								levelNext++;
							}
						} else if (StrEqual(s, "select")) {
							kwType = KeywordType::Select;
						} else if (StrEqual(s, "property")) {
							kwType = KeywordType::Property;
							if (language == Language::VBNET) {
								levelNext++;
							}
						} else if (StrEqualsAny(s, "sub", "function")) {
							levelNext++;
							if (chNext == '(' && language == Language::VBNET) {
								lambdaExpr = true;
							}
						} else if (StrEqual(s, "with")) {
							if (chNext != '{') {
								levelNext++;
							}
						} else if (StrEqual(s, "then")) {
							if (ifThenMask & 1) {
								ifThenMask |= 2;
								// check single line If ... Then ...
								if (chNext != '\0' && chNext != '\'') {
									levelNext--;
								}
							}
						} else if (language != Language::VBScript) {
							if (StrEqual(s, "enum")) {
								levelNext++;
							} else if (language == Language::VBNET) {
								if (StrEqual(s, "continue")) {
									kwType = KeywordType::End; // Continue Do, For, While
								} else if (StrEqualsAny(s, "skip", "take")) {
									kwType = KeywordType::SkipWhile;
								} else if (StrEqual(s, "custom")) {
									kwType = KeywordType::CustomEvent;
								} else if (IsCodeFolding(s, wordLen)
									|| (kwPrev == KeywordType::CustomEvent && StrEqual(s, "event"))
									|| (chNext == '(' && StrEqualsAny(s, "addhandler", "removehandler", "raiseevent"))
									|| (!foldCurrent.IsInterfaceBlock() && StrEqual(s, "structure"))) {
									levelNext++;
								}
							} else {
								if (StrEqual(s, "type") || StrStartsWith(s, "begin")) {
									levelNext++;
								}
							}
						}
					}
				} else {
					kwType = KeywordType::None;
					if (StrStartsWith(s, "#end")) {
						levelNext--;
					} else if (StrEqualsAny(s, "#if", "#region", "#externalsource")) {
						levelNext++;
					}
				}
				wordLen = 0;
			}
		} else if (style == SCE_VB_OPERATOR) {
			kwType = KeywordType::None;
			if (ch == ')' && lambdaExpr) {
				lambdaExpr = false;
				const int chNext = LexGetNextChar(styler, startPos, lineStartNext);
				// single line lambda
				if (chNext != '\0' && chNext != '_' && chNext != '\'') {
					levelNext--;
				}
			} else if (AnyOf<'{', '}'>(ch)) {
				// Anonymous With { ... }
				levelNext += ('{' + '}')/2 - ch;
			}
		} else if (!IsSpaceEquiv(style)) {
			kwType = KeywordType::None;
		}

		if (startPos == lineStartNext) {
			const FoldLineState foldNext(styler.GetLineState(lineCurrent + 1));
			levelNext = sci::max(levelNext, SC_FOLDLEVELBASE);
			if (foldCurrent.GetLineType() != 0) {
				if (foldCurrent.GetLineType() != foldPrev.GetLineType()) {
					levelNext++;
				}
				if (foldCurrent.GetLineType() != foldNext.GetLineType()) {
					levelNext--;
				}
			}

			const int levelUse = levelCurrent;
			int lev = levelUse | (levelNext << 16);
			if (levelUse < levelNext) {
				lev |= SC_FOLDLEVELHEADERFLAG;
			}
			styler.SetLevel(lineCurrent, lev);

			lineCurrent++;
			lineStartNext = styler.LineStart(lineCurrent + 1);
			levelCurrent = levelNext;
			foldPrev = foldCurrent;
			foldCurrent = foldNext;
			kwType = KeywordType::None;
			ifThenMask = 0;
			lambdaExpr = false;
		}
	}
}

}

extern const LexerModule lmVisualBasic(SCLEX_VISUALBASIC, ColouriseVBDoc, "vb", FoldVBDoc);
